{width=“75%”, height=“75%”}
| Name | iglu R function | Reference | Time Dependent |
|---|---|---|---|
| Active Percent | Yes | ||
| ADRR (Average Daily Risk Range) | Yes | ||
| AUC (Area Under the Curve) | Yes | ||
| COGI (COntinuous Glucose monitoring Index) | No | ||
| CONGA (Continuous Overall Net Glycemic Action) | Yes | ||
| Coefficient of variation (CV) | No | ||
| CVmean (mean of daily CV) | Yes | ||
| CVsd (SD of daily CV) | Yes | ||
| eA1c (Estimated A1c) | No | ||
| Episode Calculation | Yes | ||
| Episode Calculation Profile | Yes | ||
| Glucose Management Indicator (GMI) | No | ||
| GRADE (Glycemic Risk Assessment Diabetes Equation) | No | ||
| GRADEeu | No | ||
| GRADEhyper | No | ||
| GRADEhypo | No | ||
| GVP (Glucose Variability Percentage) | Yes | ||
| HBGI (High Blood Glucose Index) | No | ||
| LBGI (Low Blood Glucose Index) | No | ||
| Hyperglycemia Index Parameters | No | ||
| Hypoglycemia Index Parameters | No | ||
| IGC (Index of Glycemic Control) | No | ||
| IQR (Interquartile Range) | No | ||
| J-Index | No | ||
| M-value | No | ||
| MAD (Mean Absolute Deviation) | No | ||
| MAG (Mean Absolute Glucose) | Yes | ||
| MAGE (Mean Amplitude of Glycemic Excursions) | No | ||
| Mean | No | ||
| Median | No | ||
| MODD (Mean Of Daily Differences) | Yes | ||
| Percent Above | No | ||
| Percent Below | No | ||
| Percent in range | No | ||
| Quantiles | No | ||
| Range | No | ||
| Rate of change (ROC) | Yes | ||
| Standard Deviation of ROC | Yes | ||
| Standard Deviation | No | ||
| SdB (between days, within timepoints) | Yes | ||
| SdBDM (between days, within timepoints, corrected for changes in daily means) | Yes | ||
| SdDM (horizontal sd) | Yes | ||
| SdHHMM (between time points) | Yes | ||
| SdW (vertical within days) | Yes | ||
| SdWSH (within time series) | Yes |
---
title: Continuous Glucose Monitoring (CGM) Visualization
author: Agustin Calatroni <a href='https://github.com/agstn/CGM'> <i style='background-color:white' class='fa fa-github'> Wonderful-Wednesdays </i></a>
date: "`r format(Sys.Date(), format='%a %d %b %Y')`"
output:
flexdashboard::flex_dashboard:
self_contained: true
source_code: embed
---
```{=html}
<style>
element.style {
width: 900px;
height: 550px;
margin-top: 10px;
margin-bottom: 10px;
}
</style>
```
```{r knitr-defaults}
knitr::opts_chunk$set(warning = FALSE, message = FALSE, comment = NA)
knitr::opts_chunk$set(cache = FALSE)
options(width=170)
```
```{r load-packages}
pacman::p_load(tidyverse, rio, gt)
pacman::p_load(labelled)
pacman::p_load(patchwork)
pacman::p_load(iglu)
pacman::p_load(partykit, ggparty)
pacman::p_load(ggdist)
```
```{r import-reshape-label}
CGM_data_1 <- import('https://raw.githubusercontent.com/VIS-SIG/Wonderful-Wednesdays/master/data/2021/2021-08-11/simulated_data.csv')
CGM_data_2 <- CGM_data_1 %>%
rename(VISITNUM_N = VISITNUM) %>%
mutate(VISITNUM_C = factor(VISITNUM_N, labels = c('Baseline','26-weeks','52-weeks')),
VISITNUM_M = case_when(VISITNUM_N == 3 ~ '01',
VISITNUM_N == 17 ~ '06',
VISITNUM_N == 21 ~ '12'),
TREATMENT = factor(TREATMENT ) %>%
fct_relevel("SOC","Rx low","Rx medium","Rx high") %>%
fct_recode(`Rx-Low` = "Rx low",
`Rx-Med` = "Rx medium",
`Rx-High` = "Rx high")) %>%
rename(VALUE_ORIG = 'Original_CGMValue',
VALUE_SIM = 'Simulated_CGMValue',
TIME = 'CGMTIME',
DAY = 'CGMDAY') %>%
select(SUBJID,
TREATMENT,
VISITNUM_N,
VISITNUM_C,
VISITNUM_M,
DAY,
TIME,
VALUE_ORIG,
VALUE_SIM) %>%
mutate(DAY_TIME = as.POSIXct(str_glue("2021-{VISITNUM_M}-0{DAY} {TIME}:00")),
.after = TIME)
var_label(CGM_data_2) <- list(
SUBJID = 'Subject Identification',
TREATMENT = 'Treatment Regimen',
VISITNUM_N = 'Study visit NUM',
VISITNUM_M = 'Study visit MONTH',
VISITNUM_C = 'Study Visit NAME',
DAY = 'Day the CGM was measured',
TIME = 'Time of the day that the CGM value was measured',
DAY_TIME = 'Day/Time created',
VALUE_ORIG = 'Original CGM value',
VALUE_SIM = 'Simulated CGM value')
export(CGM_data_2, "CGM_data.rds")
```
```{r iglue-function}
plot_ranges_2 <- function (data) {
gl = id = below_54 = below_70 = in_range_70_180 = above_180 = above_250 = percent = NULL
rm(list = c("gl", "id", "below_54", "below_70", "in_range_70_180", "above_180", "above_250", "percent"))
subject = unique(data$id)
ns = length(subject)
if (ns > 1) {
subject = subject[1]
warning(paste("The provided data have", ns, "subjects. The plot will only be created for subject",
subject))
data = data %>% dplyr::filter(id == subject)
}
ranges <- agp_metrics(data, shinyformat = FALSE) %>%
dplyr::select(-c("id", "active_percent", "mean", "GMI", "CV")) %>%
dplyr::summarise(range = c("very_low", "low", "target", "high", "very_high"),
percent = c(below_54, below_70 - below_54, in_range_70_180, above_180 - above_250, above_250))
ranges = ranges %>%
dplyr::mutate(range = factor(range, levels = c("very_high", "high", "target", "low", "very_low")))
colors <- c("#F9B500", "#F9F000", "#48BA3C", "#F92D00", "#8E1B1B")
ggplot(data = ranges, mapping = aes(y = '', fill = range, x = percent)) +
geom_bar(stat = "identity") +
scale_fill_manual(values = colors,
drop = FALSE,
labels = c("Very High\n>250",
"High\n181-250",
"Target\n70-180",
"Low\n54-69",
"Very Low\n<54")) +
scale_x_continuous(breaks = seq(0, 100, 25)) +
labs(x = NULL,
y = NULL,
fill = "Percent (%) Time spent in \nstandardized glycemic ranges") +
scale_y_discrete(breaks = NULL) +
guides(fill = guide_legend(reverse = TRUE,
label.position = 'bottom'))
}
```
```{r iglu-derive}
options(warn=-1)
CGM_iglu_data <- CGM_data_2 %>%
mutate(studyid = SUBJID) %>%
process_data(id = "SUBJID", timestamp = "DAY_TIME", glu = "VALUE_ORIG") %>%
select(-visitnum_n, -visitnum_m, -time.1, -value_sim, -day) %>%
rename(visit = visitnum_c) %>%
nest_by(studyid, treatment, visit)
CGM_iglu_data %>%
ungroup() %>%
count(studyid) %>%
filter(n == 3) %>%
pull(studyid) -> CGM_n3
export(CGM_n3, "CGM_n3.rds")
CGM_iglu_metrics <- CGM_iglu_data %>%
rowwise() %>%
mutate(all_metrics = list( data %>% data.frame() %>% all_metrics() )) %>%
mutate(panel = list(
(plot_ranges_2(data)) /
(guide_area()) /
(plot_agp(data) + ylim(39, 401)) /
(plot_daily(data) + ylim(39, 401) +
geom_ribbon(aes(reltime, ymin = 70, ymax = 180), fill = "#48BA3C", alpha = 0.3) +
geom_hline(yintercept = c(70, 180), color = "#48BA3C")) /
plot_layout(heights = c(1, 1, 3, 3),
guides = 'collect') &
theme_bw() &
theme(legend.position='bottom',
legend.key.height = unit(0.3, 'cm'),
legend.key.width = unit(1.5, 'cm'),
strip.background = element_blank(),
strip.text = element_blank(),
legend.title=element_text(size=9))
)
) %>%
unnest(c(all_metrics))
export(CGM_iglu_metrics, "CGM_iglu_metrics.rds")
```
# VISUALIZATION {data-navmenu="AGP"}
```{r iglu-trelliscope}
pacman::p_load(trelliscopejs)
CGM_iglu_metrics %>%
select(studyid, treatment, visit,
ADRR, AUC, COGI, Conga, eA1C, GMI, GVP, HBGI, LBGI, IGC,
panel) %>%
ungroup() %>%
set_labels(list(ADRR = 'average daily risk range',
AUC = 'Area Under Curve',
COGI = 'Continuous GlucoseMonitoring Index',
Conga = 'Continuous Overall Net Glycemic Action',
eA1C = 'Estimated A1c',
GMI = 'Glucose Management Indicator',
GVP = 'Glucose Variability Percentage',
HBGI = 'High Blood Glucose Index',
LBGI = 'Low Blood Glucose Index',
IGC = 'Index of Glycemic Control')) %>%
mutate(across(where(is.numeric), ~round(.x, 2))) %>%
trelliscope(name = 'CGM: Continuous Glucose Monitoring Visualization',
desc = 'Display Ambulatory Glucose Profile (AGP) statistics (for subjects with ALL 3 visits)',
panel_col = 'panel',
path = './CGM-trelliscope',
ncol = 3,
nrow = 1,
height = 900,
width = 700,
state = list(sort = list(sort_spec('studyid')))
)
```
# PLOT AGP {data-navmenu="AGP"}
```{r, out.width="1000px", out.extra="data-external=1"}
pacman::p_load(webshot)
knitr::include_url("https://irinagain.github.io/iglu/reference/plot_agp.html", height = "800px")
```
# PLOT RANGES {data-navmenu="AGP"}
```{r, out.width="1000px", out.extra="data-external=1"}
knitr::include_url("https://irinagain.github.io/iglu/reference/plot_ranges.html", height = "800px")
```
# PLOT DAILY {data-navmenu="AGP"}
```{r, out.width="1000px", out.extra="data-external=1"}
knitr::include_url("https://irinagain.github.io/iglu/reference/plot_daily.html", height = "800px")
```
# MODEL RESULTS
```{r party-model}
CGM_data_m1 <- CGM_iglu_metrics %>%
select(studyid, treatment, visit, data) %>%
rowwise() %>%
mutate(below_percent(data, targets_below = 53.9)[2],
below_percent(data, targets_below = 69.9)[2],
in_range_percent(data, target_ranges = list(c(70, 180)))[2],
above_percent(data, targets_above = 180.1)[2],
above_percent(data, targets_above = 250.1)[2]) %>%
mutate(below_69.9 = below_69.9 - below_53.9,
above_180.1 = above_180.1 - above_250.1) %>%
select(-data) %>%
pivot_longer(cols = -c(1:3),
names_to = 'state',
values_to = 'pct') %>%
mutate(state = as.factor(state) %>%
fct_relevel('below_53.9',
'below_69.9',
'in_range_70_180',
'above_180.1',
'above_250.1') %>%
fct_rev())
target_plot <- CGM_data_m1 %>%
lmtree(pct ~ state | treatment + visit, data = .,
alpha = 0.10, bonferroni = FALSE) %>%
ggparty(terminal_space = 0.6, horizontal = TRUE) +
geom_edge() +
geom_edge_label() +
geom_node_label(line_list = list(aes(label = splitvar),
aes(label = gtsummary::style_pvalue(p.value, digits = 2, prepend_p = TRUE))),
line_gpar = list(list(size = 9),
list(size = 9, fontface = 'bold')),
ids = "inner") +
geom_node_plot(
gglist = list(
geom_bar(aes(x = pct, y = "", fill = state),
position = 'stack', stat = "summary", fun = "mean"),
scale_y_discrete(breaks = NULL),
scale_fill_manual(values = c("#F9B500", "#F9F000", "#48BA3C", "#F92D00", "#8E1B1B"),
drop = FALSE,
labels = c("Very High\n>250",
"High\n181-250",
"Target\n70-180",
"Low\n54-69",
"Very Low\n<54"),
guide = guide_legend(reverse = TRUE,
label.position = 'bottom')),
labs(y = NULL,
x = "Percent (%)",
fill = "Percent (%) Time spent in \nstandardized glycemic ranges"),
theme_bw(),
theme(legend.position='bottom',
legend.key.height = unit(0.3, 'cm'),
legend.key.width = unit(1.5, 'cm'),
strip.background = element_blank(),
strip.text = element_blank(),
legend.title=element_text(size=9))),
ids = "terminal",
shared_axis_labels = TRUE
)
ggsave(str_glue("CGM-models/CGM_target.png"),
plot = target_plot,
scale = 4,
width = 900, height = 600, units = 'px')
```
{width="75%", height="75%"}
# DEFINITIONS {data-navmenu="OTHER METRICS"}
```{r}
CGM_metric <- import("CGM_metric.csv")
CGM_metric %>%
gt() %>%
cols_move_to_end(Metric_Time_dependent) %>%
cols_label(Metric_Name = "Name",
Metric_Iglu = "iglu R function",
Metric_Time_dependent = "Time Dependent") %>%
fmt_missing(Reference, missing_text = "-") %>%
fmt_markdown(c(Reference, Metric_Iglu)) %>%
cols_align('left') %>%
cols_width(Metric_Name ~ px(450),
Metric_Iglu ~ px(250),
Reference ~ px(250)) %>%
tab_options(table.font.size = "medium",
data_row.padding = px(2),
footnotes.padding = px(2),
source_notes.padding = px(2))
```
# MODEL RESULTS {data-navmenu="OTHER METRICS"}
```{r party-mod-viz}
# trick: https://www.johannesbgruber.eu/post/2021-07-28-let-users-choose-which-plot-you-want-to-show/
for(i in c("ADRR","AUC", "COGI", "Conga", "eA1C", "GMI", "GVP", "HBGI", "LBGI", "IGC")){
metric_run <- sym(paste(i))
metric_lab <- CGM_metric %>%
filter(str_detect(Metric_Name, paste(metric_run))) %>%
pull(Metric_Name)
metric_plot <- CGM_iglu_metrics %>%
lmtree(formula(str_glue("{metric_run} ~ 1 | treatment + visit")),
data = .,
alpha = 0.10, bonferroni = FALSE) %>%
ggparty(terminal_space = 0.4, horizontal = TRUE) +
geom_edge() +
geom_edge_label() +
geom_node_label(line_list = list(aes(label = splitvar),
aes(label = gtsummary::style_pvalue(p.value, digits = 2, prepend_p = TRUE))),
line_gpar = list(list(size = 9),
list(size = 9, fontface = 'bold')),
ids = "inner") +
geom_node_plot(
gglist = list(
stat_halfeye(aes(x = !!metric_run, y = ""),
point_interval = mean_qi,
justification = -0.05),
geom_rug(aes(x = !!metric_run), alpha = 0.25, sides = 't', length = unit(0.05, "npc")),
geom_boxplot(aes(x = !!metric_run, y = ""),
width = 0.30, outlier.shape = NA,
position = position_nudge(y = -0.25)),
scale_y_discrete(breaks = NULL),
labs(y = NULL,
x = metric_lab),
theme_bw()),
ids = "terminal",
shared_axis_labels = TRUE)
ggsave(str_glue("CGM-models/{metric_run}.png"),
plot = metric_plot,
scale = 4,
width = 850, height = 600, units = 'px')
}
```
<select id="imgList">
<option value="CGM-models/ADRR.png">ADRR (average daily risk range)</option>
<option value="CGM-models/AUC.png">AUC (Area Under Curve)</option>
<option value="CGM-models/COGI.png">COGI (Continuous GlucoseMonitoring Index)</option>
<option value="CGM-models/Conga.png">CONGA (Continuous Overall Net Glycemic Action)</option>
<option value="CGM-models/eA1C.png">eA1c (Estimated A1c)</option>
<option value="CGM-models/GMI.png">GMI (Glucose Management Indicator)</option>
<option value="CGM-models/GVP.png">GVP (Glucose Variability Percentage)</option>
<option value="CGM-models/HBGI.png">HBGI (High Blood Glucose Index)</option>
<option value="CGM-models/LBGI.png">LBGI (Low Blood Glucose Index)</option>
<option value="CGM-models/IGC.png">IGC (Index of Glycemic Control)</option>
</select>
<img src="CGM-models/ADRR.png" id="image" width="75%" height="75%"/>
<script type="text/javascript">
var img = document.getElementById("image");
function setClass(e) {
var select = e.target;
img.src = select.options[select.selectedIndex].value;
return false;
}
document.getElementById("imgList").onchange = setClass;
</script>